<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Class</title>
	</head>
	<body>
		<script type="text/javascript">
			// JavaScript 语言中，生成实例对象的传统方法是通过函数构造
			function Point1(x, y) {
				this.x = x;
				this.y = y;
			}

			Point1.prototype.toString = function() {
				return '(' + this.x + ', ' + this.y + ')';
			};

			// ES6 提供了更接近传统语言的写法，引入了 Class（类）这个概念，作为对象的模板。通过class关键字，可以定义类。但这仅仅只是语法糖而已，本质也是函数，新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
			// 因为本质也是函数，事实上，类的所有方法都定义在类的prototype属性上面
			class Point2 {
				constructor(x, y) {
					this.x = x;
					this.y = y;
				}

				toString() {
					return '(' + this.x + ', ' + this.y + ')';
				}
			}
			console.log(typeof Point2); //function

			// 与 ES5 一样，类的所有实例共享一个原型对象
			var p21 = new Point2(2, 3);
			var p22 = new Point2(3, 2);
			console.log(p21.__proto__ === p22.__proto__) //true

			// 取值函数（getter）和存值函数（setter）
			// 与 ES5 一样，在“类”的内部可以使用get和set关键字，对某个属性设置存值函数和取值函数，拦截该属性的存取行为
			class MyClass {
				constructor() {
					// ...
				}
				get prop() {
					return 'getter';
				}
				set prop(value) {
					console.log('setter: ' + value);
				}
			}
			let inst = new MyClass();
			inst.prop = 123; // setter: 123
			inst.prop // 'getter'

			// 类相当于实例的原型，所有在类中定义的方法，都会被实例继承。如果在一个方法或属性前，加上static关键字，就表示该方法或该属性不会被实例继承，而是直接通过类来调用，这就称为“静态方法”或“静态属性”
			// 注意，如果静态方法包含this关键字，这个this指的是类，而不是实例
			class Foo {
				static myStaticProp = 42;
				static classMethod() {
					return 'hello';
				}
			}
			console.log(Foo.myStaticProp); // 42
			console.log(Foo.classMethod()); // 'hello'
			var foo = new Foo();
			console.log(foo.myStaticProp); // undefined
			// console.log(foo.classMethod()); // TypeError: foo.classMethod is not a function

			// 私有属性或方法
			// 之所以要引入一个新的前缀#表示私有属性，而没有采用private关键字，是因为 JavaScript 是一门动态语言，没有类型声明，使用独立的符号似乎是唯一的比较方便可靠的方法，能够准确地区分一种属性是否为私有属性
			class IncreasingCounter {
				#a = 0;
				#b = 0;
				increment() {
					this.#a++;
					this.#b++;
				}
				#sum() {
					return this.#a + this.#b;
				}
				printSum() {
					console.log(this.#sum());
				}
			}

			const counter = new IncreasingCounter();
			counter.increment();
			counter.printSum() // 2
			// console.log(counter.#a) // 报错
			// console.log(counter.#sum()) // 报错

			// 注意点
			//（1）类和模块的内部，默认就是严格模式，所以不需要使用use strict指定运行模式
			//（2）类不存在变量提升（hoist），这一点与 ES5 完全不同
			//（3）由于本质上，ES6 的类只是 ES5 的构造函数的一层包装，所以函数的许多特性都被Class继承，包括name属性
			//（4）如果某个方法之前加上星号（*），就表示该方法是一个 Generator 函数
			//（5）类的方法内部如果含有this，它默认指向类的实例。但是，必须非常小心，一旦单独使用该方法，很可能报错
			// Class 的继承
			// Class 可以通过extends关键字实现继承，这比 ES5 的通过修改原型链实现继承，要清晰和方便很多。
			class Point {
				constructor(x, y) {
					this.x = x;
					this.y = y;
				}
			}
			class ColorPoint extends Point {
				constructor(x, y, color) {
					super(x, y); // 调用父类的constructor(x, y)
					this.color = color;
				}
			}

			// 类的 prototype 属性和__proto__属性
			// 大多数浏览器的 ES5 实现之中，每一个对象都有__proto__属性，指向对应的构造函数的prototype属性。Class 作为构造函数的语法糖，同时有prototype属性和__proto__属性，因此同时存在两条继承链。
			//（1）子类的__proto__属性，表示构造函数的继承，总是指向父类。
			//（2）子类prototype属性的__proto__属性，表示方法的继承，总是指向父类的prototype属性
			ColorPoint.__proto__ === Point // true
			ColorPoint.prototype.__proto__ === Point.prototype // true
		</script>
	</body>
</html>
